import operator

import allure
import jsonpath

from utils.mysql_tool.connect_mysql import ConnectMysql
from utils.logging_tool.recordlog import logs
from config.operationConfig import OperationConfig


class Assertions:


    @allure.step("响应文本断言：状态码/包含")
    def assert_contains(self, value, response, status_code):
        """
        字符串包含断言模式，断言预期结果的字符串是否包含在接口的响应信息中
        :param value: 预期结果，yaml文件的预期结果值
        :param response: 接口实际响应结果
        :param status_code: 响应状态码
        :return: 返回结果的状态标识
        """
        # 断言状态标识，0成功，其他失败
        flag = 0
        for assert_key, assert_value in value.items():
            if assert_key == "status_code":  # 状态码断言
                if assert_value == status_code:
                    logs.info(f'响应状态码断言成功：接口返回码{status_code} == {assert_value}')
                    allure.attach(f'预期结果：{assert_value}\n实际结果：{status_code}', '响应状态码断言结果：成功',
                                  attachment_type=allure.attachment_type.TEXT)
                if assert_value != status_code:
                    flag += 1
                    logs.error(f'contains断言失败：接口返回码{status_code} ！= {assert_value}')
                    allure.attach(f'预期结果：{assert_value}\n实际结果：{status_code}', '响应代码断言结果：失败',
                                  attachment_type=allure.attachment_type.TEXT)
            else:  # 字符串包含断言
                resp_list = jsonpath.jsonpath(response, f'$..{assert_key}')
                if isinstance(resp_list[0], str):
                    resp_list = ''.join(resp_list)
                if resp_list:
                    assert_value = None if assert_value.upper() == 'NONE' else assert_value
                    if assert_value in resp_list:
                        logs.info(f'字符串包含断言成功：预期结果{assert_value} == 实际结果{resp_list}')
                    else:
                        flag = flag + 1
                        logs.error(f'响应文本断言失败：预期结果为{assert_value} ！= 实际结果为{resp_list}')
                        allure.attach(f"预期结果：{assert_value}\n实际结果：{resp_list}", '响应文本断言结果：失败',
                                      attachment_type=allure.attachment_type.TEXT)
        return flag

    @allure.step("响应断言：相等")
    def assert_equal(self, expected_results, actual_results, status_code=None):
        """
        相等断言模式
        :param expected_results: 预期结果，yaml文件validation值
        :param actual_results: 接口实际响应结果
        :return:
        """
        flag = 0
        if isinstance(actual_results, dict) and isinstance(expected_results, dict):
            # 找出实际结果与预期结果共同的key
            if actual_results.keys().__len__() == 0:
                flag += 1
                logs.error(f"相等断言失败：actual_results 为空")
                allure.attach(f"相等断言失败", '相等断言结果：失败',
                              attachment_type=allure.attachment_type.TEXT)
                return flag
            common_keys = list(expected_results.keys() & actual_results.keys())[0]
            # 根据相同的key去实际结果中获取，并重新生成一个实际结果的字典
            new_actual_results = {common_keys: actual_results[common_keys]}
            eq_assert = operator.eq(new_actual_results, expected_results)
            if eq_assert:
                logs.info(f"相等断言成功：接口实际结果：{new_actual_results} == 预期结果：" + str(expected_results))
                allure.attach(f"预期结果：{str(expected_results)}\n实际结果：{new_actual_results}", '相等断言结果：成功',
                              attachment_type=allure.attachment_type.TEXT)
            else:
                flag += 1
                logs.error(f"相等断言失败：接口实际结果{new_actual_results} != 预期结果：" + str(expected_results))
                allure.attach(f"预期结果：{str(expected_results)}\n实际结果：{new_actual_results}", '相等断言结果：失败',
                              attachment_type=allure.attachment_type.TEXT)
        else:
            raise TypeError('相等断言--类型错误，预期结果和接口实际响应结果必须为字典类型！')
        return flag

    @allure.step("响应断言：不相等")
    def assert_not_equal(self, expected_results, actual_results, status_code=None):
        """
        不相等断言模式
        :param expected_results: 预期结果，yaml文件validation值
        :param actual_results: 接口实际响应结果
        :return:
        """
        flag = 0
        if isinstance(actual_results, dict) and isinstance(expected_results, dict):
            # 找出实际结果与预期结果共同的key
            common_keys = list(expected_results.keys() & actual_results.keys())[0]
            # 根据相同的key去实际结果中获取，并重新生成一个实际结果的字典
            new_actual_results = {common_keys: actual_results[common_keys]}
            eq_assert = operator.ne(new_actual_results, expected_results)
            if eq_assert:
                logs.info(f"不相等断言成功：接口实际结果：{new_actual_results}， != 预期结果：" + str(expected_results))
                allure.attach(f"预期结果：{str(expected_results)}\n实际结果：{new_actual_results}", '不相等断言结果：成功',
                              attachment_type=allure.attachment_type.TEXT)
            else:
                flag += 1
                logs.error(f"不相等断言失败：接口实际结果{new_actual_results}， == 预期结果：" + str(expected_results))
                allure.attach(f"预期结果：{str(expected_results)}\n实际结果：{new_actual_results}", '不相等断言结果：失败',
                              attachment_type=allure.attachment_type.TEXT)
        else:
            raise TypeError('不相等断言--类型错误，预期结果和接口实际响应结果必须为字典类型！')
        return flag

    @allure.step("响应断言：任意值")
    def assert_response_any(self, actual_results, expected_results):
        """
        断言接口响应信息中的body的任何属性值（关键字）
        :param actual_results: 接口实际响应信息
        :param expected_results: 预期结果，在接口返回值的任意值
        :return: 返回标识,0表示测试通过，非0则测试失败
        """
        flag = 0
        try:
            exp_key = list(expected_results.keys())[0]
            if exp_key in actual_results:
                act_value = actual_results[exp_key]
                rv_assert = operator.eq(act_value, list(expected_results.values())[0])
                if rv_assert:
                    logs.info("响应结果任意值断言成功")
                else:
                    flag += 1
                    logs.error("响应结果任意值断言失败")
        except Exception as e:
            logs.error(e)
            raise
        return flag

    @allure.step("响应断言-响应时间-待定")
    def assert_response_time(self, res_time, exp_time):
        """
        通过断言接口的响应时间与期望时间对比,接口响应时间小于预期时间则为通过
        :param res_time: 接口的响应时间
        :param exp_time: 预期的响应时间
        :return:
        """
        flag = 0
        try:
            if res_time < exp_time:
                logs.info('响应时间断言成功：接口响应时间[%ss] < 预期时间[%ss]' % (res_time, exp_time))
        except Exception as e:
            flag += 1
            logs.error('响应时间断言失败：接口响应时间[%ss] >= 预期时间[%ss]' % (res_time, exp_time))
            raise

    @allure.step("数据库断言")
    def assert_mysql_db(self, database, sql, rows):
        """
        数据库断言
        :param database：数据库名
        :param sql: SQL语句
        :param rows：期望查询结果行数
        :return: 返回flag标识，0表示正常，非0表示测试不通过
        """
        flag = 0
        variables = [database, sql, rows]
        # 检查多个变量是否为None
        if any(var is None for var in variables):
            flag += 1
            logs.error(f'字段[database]、[sql]、[rows] 存在空值，数据库断言失败')
        else:
            # 列表中的key 值是变量 取值时拼接
            key_list = ["CASE WHEN COUNT(*) = ", str(rows), " THEN 'TRUE' ELSE 'FALSE' END"]
            key = "".join(key_list)
            # 不填写 默认 v17，返回结果为列表嵌套字典，从列表[0]中取出字典，根据key从字典中取值（TRUE 或者 FALSE）
            db_value_dict = ConnectMysql(database).assert_execute_sql(sql, rows)[0]
            if key in db_value_dict:
                db_value = db_value_dict.get(key)

                if db_value == 'TRUE':
                    logs.info(f"数据库断言成功：数据库查询实际结果：{db_value} == 预期结果：TRUE")
                    allure.attach(f"预期结果：TRUE\n实际结果：{db_value}", '数据库断言结果：成功',
                                  attachment_type=allure.attachment_type.TEXT)
                else:
                    flag += 1
                    logs.error("数据库断言失败：数据库查询实际结果：{db_value} != 预期结果：TRUE")
                    allure.attach(f"预期结果：True\n实际结果：{db_value}", '数据库断言结果：失败',
                                  attachment_type=allure.attachment_type.TEXT)
            else:
                flag += 1
                logs.error("数据库断言失败")
        return flag

    @allure.step("断言")
    def assert_result(self, expected, response, status_code):
        """
        断言，通过断言all_flag标记，all_flag==0表示测试通过，否则为失败
        数据库断言根据key值（包括数据库）获取，不需要再写数据库名字
        :param expected: 预期结果
        :param response: 实际响应结果
        :param status_code: 响应code码
        :return:
        """
        all_flag = 0
        try:
            logs.info("测试用例预期结果：%s" % expected)
            # logs.info("实际结果：%s" % response)
            # all_flag = 0
            for yq in expected:
                for key, value in yq.items():
                    # 文本字符串包含模式断言对比
                    if key == "contains":
                        flag = self.assert_contains(value, response, status_code)
                        all_flag = all_flag + flag

                    # 相等断言模式
                    elif key == "eq":
                        flag = self.assert_equal(value, response)
                        all_flag = all_flag + flag

                    # 不相等断言
                    elif key == 'ne':
                        flag = self.assert_not_equal(value, response)
                        all_flag = all_flag + flag

                    # 任意值断言
                    elif key == 'rv':
                        flag = self.assert_response_any(actual_results=response, expected_results=value)
                        all_flag = all_flag + flag

                    # 响应时间断言
                    elif key == 'rt':
                        flag = self.assert_response_time(value,response)
                        all_flag = all_flag + flag

                    # 数据库断言
                    elif key == 'db':
                        try:
                            # database 字段不存在默认为 pid 配置下的数据库
                            database = value.get('database', OperationConfig().get_section_mysql_pid('database'))
                            sql = value.get('sql', None)
                            rows = str(value.get('rows', 1))  # rows 字段不存在默认为1
                            flag = self.assert_mysql_db(database, sql, rows)
                            # print(f'database:{database}，sql:{sql}，rows:{rows}')
                            all_flag = all_flag + flag
                        except Exception as e:
                            all_flag += 1
                            logs.error(f'数据库断言失败，err={e}')
                    else:
                        all_flag += 1
                        logs.error("不支持此种断言方式,断言失败")

        except Exception as exceptions:
            logs.error('接口断言异常，请检查yaml预期结果值是否正确填写!')
            raise exceptions

        if all_flag == 0:
            logs.info("全部断言成功")
            assert True
        else:
            logs.error("断言失败！！！")
            assert False
